<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>阿林字幕工具</title>
  <link href="./zm_res/main.css?2" rel="stylesheet">
</head>

<body>
  <div class="wrap">
    <div>
      <p class="title">通过文本生成srt字幕</p>
      <a class="title-help" href="https://www.xiaohongshu.com/discovery/item/673c9af30000000007025c47?source=webshare&xhsshare=pc_web&xsec_token=ABNoKxv6P-luvN2NDahC0JWkyMt-1Qcqzgu7a8SJ4utQQ=&xsec_source=pc_share" target="_blank">查看使用教程</a>

    </div>
    <div class="setting">
      <div class="item-input">
        <div class="text">一个字的时间(秒)</div>
        <input class="gin" type="text" id="duration" placeholder="Type something...">
      </div>
      <div class="item-input">
        <div class="text">句子间隔时间(秒)</div>
        <input class="gin" type="number" id="gap" onkeyup="value=sanitizeNumber(value)">
      </div>
    </div>

    <textarea id="inputText" placeholder="请在此输入文本" style="margin-top: 10px;"></textarea><br>
    <div  class="btn-box">
      <button class="btn" onclick="generateSRT()">生成</button>
      <button class="btn" onclick="copySRT()">复制</button>
      <a class="btn qqlink" target="_blank" href="https://qm.qq.com/q/JNtqrWaEuI">QQ交流群 533058764</a>
    </div>
    <div id="file-div" class="btn-box">
      <button class="btn" onclick="setDir()">设置文件夹</button>
      <button class="btn" onclick="saveFile()">另存为</button>
      <button class="btn" onclick="saveFile(1)">保存到文件夹</button>
      <span id="dir"></span>

    </div>
    <div id="info">无操作...</div>

    <textarea id="outputSRT" placeholder="字幕将生成到这里" style="margin-top: 10px;"></textarea>

    <div class="else-link">阿林工具系列:&nbsp;<a href="http://cj.cyjx.cool/player" target="_blank">阿林播放器</a>&nbsp;<a href="https://gitee.com/yunyuehuang/unzip_tool" target="_blank">阿林饥荒工具</a></div>

    <div class="dashang" id="dashang" onclick="showModel()">支持作者</div>

    <div class="modbox">
      <p>您的支持是我的动力~</p>
      <img src="./zm_res/qr.png" alt="">
      <div class="btn center" onclick="hideModel()">完成</div>
    </div>
  </div>

  <script>
    const DbName = "fileHandlesDB"
    const TableName = "handles"

    const SavingText = "文件保存中...请稍后..."

    const duInput = document.getElementById('duration');
    const gapInput = document.getElementById('gap');
    const orgText = document.getElementById('inputText');
    const outputSRT = document.getElementById('outputSRT');
    const infoSpan = document.getElementById('info');
    const dirSpan = document.getElementById('dir');
    let indexDbObj
    let directoryHandle

    document.addEventListener('DOMContentLoaded', async () => {
      let data = localStorage.getItem('duration');
      if (data == null) {
        data = 0.2
      }
      duInput.value = data;
      data = localStorage.getItem('gap');
      if (data == null) {
        data = 1
      }
      gapInput.value = data;
      data = localStorage.getItem('inputText');
      if (data !== null) {
        orgText.value = data;
      }

      if (!window.showDirectoryPicker) {
        document.getElementById('file-div').style.display = 'none'
        showInfo("你的浏览器不支持直接输出srt文件，请使用最新版谷歌浏览器")
      } else {
        indexDbObj = await initIndexDb()
        directoryHandle = await readHandle()
        if (directoryHandle) {
          dirSpan.textContent = "当前输出路径：" + directoryHandle.name
        }
      }
    });

    duInput.addEventListener('input', () => {
      const currentValue = duInput.value;
      localStorage.setItem('duration', currentValue);
    });

    gapInput.addEventListener('input', () => {
      const currentValue = gapInput.value;
      localStorage.setItem('gap', currentValue);
    });
    orgText.addEventListener('input', () => {
      const currentValue = orgText.value;
      localStorage.setItem('inputText', currentValue);
    });

    async function setDir() {
      try {
        let newDirectoryHandle = await window.showDirectoryPicker()
        directoryHandle = newDirectoryHandle
        dirSpan.textContent = "当前输出路径：" + directoryHandle.name
        await saveHandle(newDirectoryHandle)
      } catch (error) {
        console.log("setDir err", error)
      }
    }

    function sanitizeNumber(input) {
      // 1. 过滤非数字和小数点
      let value = input.replace(/[^\d.]/g, '');

      // 2. 处理多个小数点：只保留第一个
      value = value.replace(/\./g, (match, offset, str) => {
        return offset === str.indexOf('.') ? match : '';
      });

      // 3. 处理开头或结尾的小数点（可选）
      value = value.replace(/^\./, '0.'); // 开头的小数点转为 "0."
      value = value.replace(/\.$/, '');     // 删除末尾的小数点

      return value;
    }

    function toFix(num, dot=2){
      return Math.round(num * (10 ** dot)) / (10 ** dot); // 3.14 (number)
    }

    function generateSRT() {
      let du = Number(duInput.value)
      let gap = Number(gapInput.value)

      const inputText = orgText.value;
      const outputSRT = document.getElementById('outputSRT');
      const lines = inputText.split('\n');
      let srtContent = '';
      let currentTime = 0;
      let lineNum = 0
      lines.forEach((line, index) => {
        if (line.trim() === '') return; // 跳过空行

        let isTime = parseTimeToSeconds(line)
        if (isTime) {
          currentTime = toFix(currentTime + isTime)
          return;
        }
        const duration = line.length * du;
        const startTime = formatTime(currentTime);
        currentTime = toFix(currentTime + duration)
        const endTime = formatTime(currentTime);
        currentTime = toFix(currentTime + gap)
        lineNum++
        srtContent += `${lineNum}\n`;
        srtContent += `${startTime} --> ${endTime}\n`;
        srtContent += `${line.trim()}\n\n`;
      });

      outputSRT.value = srtContent;
    }

    function copySRT() {
      generateSRT()

      navigator.clipboard.writeText(outputSRT.value)
        .then(() => {
          showInfo("已复制")
        })
        .catch(err => {
          console.error('复制失败：', err);
        });
    }

    function parseTimeToSeconds(timeString) {
      // 正则表达式匹配 "MM:SS" 格式
      const regex = /^(\d{2}):(\d{2})$/;
      const match = timeString.match(regex);

      if (match) {
        // 提取分钟和秒
        const minutes = parseInt(match[1], 10);
        const seconds = parseInt(match[2], 10);

        // 计算总秒数
        return minutes * 60 + seconds;
      } else {
        // 如果不匹配，返回 null 或者抛出错误
        return null;
      }
    }

    function formatTime(totalSeconds) {
      // 将总秒数转换为整数毫秒，处理四舍五入和进位
      const totalMilliseconds = Math.round(totalSeconds * 1000);

      // 分解小时、分钟、秒和毫秒
      const hours = Math.floor(totalMilliseconds / (3600 * 1000));
      let remaining = totalMilliseconds % (3600 * 1000);

      const minutes = Math.floor(remaining / (60 * 1000));
      remaining = remaining % (60 * 1000);

      const seconds = Math.floor(remaining / 1000);
      const milliseconds = remaining % 1000;

      // 补零格式化
      const pad = (num, length) => num.toString().padStart(length, '0');
      return `${hours}:${pad(minutes, 2)}:${pad(seconds, 2)},${pad(milliseconds, 3)}`;


    }


    async function saveFile(type) {
      if (infoSpan.textContent.indexOf(SavingText) > -1) {
        return
      }
      generateSRT()
      if (!outputSRT.value) {
        showInfo("无内容")
        return
      }
      try {
        showInfo(SavingText)
        console.log("saveFile directoryHandle", directoryHandle)

        if (type == 1) { //save dir
          if (!directoryHandle) {
            directoryHandle = await readHandle()
            console.log("saveFile directoryHandle after read", directoryHandle)
          }

          if (directoryHandle) { //如果句柄是之前保存的，检查权限
            hasAccess = await checkAccess(directoryHandle)
            if (!hasAccess) {
              showInfo("没有操作权限，请再次尝试，并同意授权")
              return
            }
          }

          if (!directoryHandle) {
            showInfo("")
            alert("请先设置文件夹")
            return
          }
        }

        fileName = getFormattedDateTime()
        if (type == 1) {
          await saveDir(directoryHandle, fileName, outputSRT.value);
        } else {
          await saveAs(fileName, outputSRT.value)
        }

        showInfo(`文件已保存：${directoryHandle.name}/${fileName}`)

      } catch (error) {
        if (error instanceof DOMException && error.name === 'NotFoundError') {
          directoryHandle = null
          showInfo("")
          alert("文件夹不存在，请重新设置文件夹")
          return
        }
        showInfo("文件保存错误")
        console.log('选择目录或写入文件时发生错误:', error);
      }
    }

    async function saveDir(directoryHandle, fileName, content) {
      // 创建文件
      showSaveInfo("创建文件")
      const fileHandle = await directoryHandle.getFileHandle(fileName + ".txt", { create: true });
      showSaveInfo("创建流")
      const writableStream = await fileHandle.createWritable();

      showSaveInfo("写入内容")
      await writableStream.write(content);

      showSaveInfo("落盘")
      await writableStream.close();
      showSaveInfo("重命名")
      await fileHandle.move(fileName + ".srt");
    }

    async function saveAs(fileName, content) {
      let opts = {
        suggestedName: fileName + ".srt",
        types: [{
          description: 'srt文件',
          accept: {  'text/plain': ['.srt'],},
        }],
      };
      if (directoryHandle) {
        opts.startIn = directoryHandle
      }
      showSaveInfo("选择路径")
      const fileHandle = await window.showSaveFilePicker(opts);  //通过这个方式获取到的句柄，保存文件速度很快。通过directoryHandle.getFileHandle获得的文件句柄，调用writableStream.close的时候，会出现一个临时文件，然后过了很久才完成保存。

      showSaveInfo("创建流")
      const writableStream = await fileHandle.createWritable();

      showSaveInfo("写入内容")
      await writableStream.write(content);

      showSaveInfo("落盘")
      await writableStream.close();
    }


    async function checkAccess(directoryHandle) {
      const permission = await directoryHandle.requestPermission({ mode: 'readwrite' });
      console.log("checkAccess", permission)
      if (permission === 'granted') {
        return true
      }
      return false
    }

    function getFormattedDateTime() {
      const now = new Date();

      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, '0');  // 月份是从0开始的，所以需要加1
      const day = String(now.getDate()).padStart(2, '0');

      const hours = String(now.getHours()).padStart(2, '0');
      const minutes = String(now.getMinutes()).padStart(2, '0');
      const seconds = String(now.getSeconds()).padStart(2, '0');

      return `${year}${month}${day} ${hours}${minutes}${seconds}`;
    }

    function clearDB() {
      if (!indexDbObj) {
        return
      }

      const transaction = indexDbObj.transaction([TableName], 'readwrite');
      const objectStore = transaction.objectStore(TableName);
      objectStore.clear().onsuccess = function (event) {
        console.log('All records in ' + objectStoreName + ' have been deleted.');
      };

      objectStore.clear().onerror = function (event) {
        console.error('Failed to clear object store: ' + event.target.errorCode);
      };
      transaction.oncomplete = function (event) {
        // 可以选择关闭数据库连接，但通常不需要，因为连接会在没有活动事务时自动关闭
        db.close();
      };
      transaction.onerror = function (event) {
        console.error('Transaction failed: ' + event.target.errorCode);
      };
    }

    function initIndexDb() {
      if (!window.indexedDB) {
        console.error('IndexedDB 不受支持');
        return null;
      }
      return new Promise((resolve, reject) => {
        const dbRequest = indexedDB.open(DbName, new Date().getTime());
        dbRequest.onupgradeneeded = (event) => {
          const db = event.target.result;

          if (!db.objectStoreNames.contains(TableName)) {
            db.createObjectStore(TableName, { keyPath: "id" });
            console.log('create handles')
          }

        };
        dbRequest.onsuccess = (event) => {
          resolve(event.target.result)
          console.log('db init succ')
        };
      })
    }

    async function saveHandle(directoryHandle) {
      if (!indexDbObj) {
        return
      }
      const transaction = indexDbObj.transaction(TableName, 'readwrite');
      const store = transaction.objectStore(TableName);
      const req = store.put({ id: 'directory', handle: directoryHandle });
      req.onsuccess = function (e) {
        console.log('save db', e)
      }

      req.onerror = function (e) {
        console.log('save db err', e)
      }
    }

    async function readHandle() {
      if (!indexDbObj) {
        return
      }

      return new Promise((resolve, reject) => {
        console.log("db get data begin")
        let transaction
        try {
          transaction = indexDbObj.transaction(TableName, 'readonly');
        } catch (error) {
          console.log("read db err", error)
          resolve(null);
          return
        }

        const reader = transaction.objectStore(TableName).get('directory')
        reader.onsuccess = () => {
          console.log("db get data", reader.result)
          if (reader.result) {
            resolve(reader.result.handle);
          } else {
            resolve(null);
            console.log('no data')
          }
        };

        reader.onerror = () => {
          console.log('reader err')
          resolve(null);
        };

      });
    }

    function showInfo(msg) {
      infoSpan.textContent = msg
    }

    function showSaveInfo(msg){
      infoSpan.textContent = `文件保存中...${msg}...`
    }


    function showModel(){
      document.getElementsByClassName("modbox")[0].style.display = "block"
    }

    function hideModel(){
      console.log(document.getElementsByClassName("modbox"))
      document.getElementsByClassName("modbox")[0].style.display = "none"
    }

    setInterval(() => {
      const mTime = localStorage.getItem("mtime")
      const nowTime = Math.floor(Date.now() / 1000)
      if (mTime && nowTime - Number(mTime) < 86400 && Number(mTime) < nowTime) {
        return
      }
      showModel()
      localStorage.setItem("mtime", nowTime.toString())
    }, 20*1000)


  </script>
  <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?396f599565112345e27fbe1b506f7150";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>
</body>

</html>
